package jmine.tec.rpc.server.representers;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jmine.tec.rpc.common.serialization.MockBean;
import jmine.tec.rpc.common.serialization.MockBeanBuilder;
import jmine.tec.rpc.server.representation.RepresentationFactory;

import org.junit.Before;
import org.junit.Test;

/**
 * Teste do RepresenterFactory
 * 
 * @author lundberg
 */
public class ARepresentationFactoryShould {

    private RepresentationFactory factory;

    /**
     * {@inheritDoc}
     */
    @Before
    public void init() {
        this.factory = BaseRepresentationFactoryBuilder.aRepresentationFactory().build();
    }

    /**
     * Testa a conversão de valores nulos
     */
    @Test
    public void convertNullValue() {
        assertThat(this.factory.representObject(Object.class, null), is(equalTo(null)));
    }

    /**
     * Testa a conversão de inteiros
     */
    @Test
    public void convertIntegers() {
        assertThat(this.factory.representObject(Integer.class, 1), is(equalTo(1)));
    }

    /**
     * Testa a conversão de longs
     */
    @Test
    public void convertLongs() {
        assertThat(this.factory.representObject(Long.class, 1L), is(equalTo(1L)));
    }

    /**
     * Testa a conversão de BigDecimals
     */
    @Test
    public void convertBigDecimals() {
        assertThat(this.factory.representObject(BigDecimal.class, BigDecimal.ZERO), is(equalTo(BigDecimal.ZERO)));
    }

    /**
     * Testa a conversão de Class
     */
    @Test
    public void convertClass() {
        assertThat(this.factory.representObject(Class.class, BigDecimal.class), is(equalTo((Object) BigDecimal.class)));
    }

    /**
     * Testa a conversão de strings
     */
    @Test
    public void convertStrings() {
        assertThat(this.factory.representObject(String.class, "test"), is(equalTo("test")));
    }

    /**
     * Testa a conversão de listas
     */
    @Test
    public void convertLists() {
        List<Integer> list = Arrays.asList(new Integer[]{ 1, 2, 1 });
        assertThat(this.factory.representObjects(Integer.class, list), is(equalTo(list)));
    }

    /**
     * Testa a conversão de listas
     */
    @Test
    public void convertListsIntoNewLists() {
        List<Integer> list = Arrays.asList(new Integer[]{ 1, 2, 1 });
        assertThat(this.factory.representObjects(Integer.class, list) == list, is(false));
    }

    /**
     * Testa a conversão de sets
     */
    @Test
    public void convertSets() {
        Set<String> set = new HashSet<String>(Arrays.asList(new String[]{ "1", "2", "a" }));
        assertThat(this.factory.representObjects(String.class, set), is(equalTo(set)));
    }

    /**
     * Testa a conversão de listas
     */
    @Test
    public void convertSetsIntoNewSets() {
        Set<String> set = new HashSet<String>(Arrays.asList(new String[]{ "1", "2", "a" }));
        assertThat(this.factory.representObjects(String.class, set) == set, is(false));
    }

    /**
     * Testa a conversão de sets
     */
    @Test
    public void convertMaps() {
        Map<String, Integer> map = this.aMap();
        assertThat(this.factory.representObjects(String.class, Integer.class, map), is(equalTo(map)));
    }

    /**
     * Testa a conversão de listas
     */
    @Test
    public void convertMapsIntoNewMaps() {
        Map<String, Integer> map = this.aMap();
        assertThat(this.factory.representObjects(String.class, Integer.class, map) == map, is(false));
    }

    /**
     * Testa a representação de referências circulares
     */
    @Test
    public void convertObjectWithCircularReferences() {
        MockBean mock = MockBeanBuilder.aBean().build();
        mock.getObjectList().add(mock);
        MockRepresentation representation = this.factory.representObject(MockRepresentation.class, mock);
        assertThat(representation == representation.getObjectList().get(0), is(true));
    }

    /**
     * Testa a conversão de listas
     */
    @Test
    public void convertBeansThroughReflection() {
        MockBean bean = MockBeanBuilder.aBean().build();
        bean.getObjectList().add(MockBeanBuilder.aBean().withString("anotherString").build());
        bean.getObjectList().add(MockBeanBuilder.aBean().withString("evenAnotherString").build());
        bean.getSet().add(MockBeanBuilder.aBean().withString("String!!").build());
        bean.getMap().put("key", MockBeanBuilder.aBean().withString("String!!").build());
        this.assertMockBeanRepresentationEquality(bean, this.factory.representObject(MockRepresentation.class, bean));
    }

    /**
     * @param bean bean
     * @param representation representation
     */
    protected void assertMockBeanRepresentationEquality(MockBean bean, MockRepresentation representation) {
        assertThat(representation.getBool1(), is(equalTo(bean.getBool1())));
        assertThat(representation.getBooleanList(), is(equalTo(bean.getBooleanList())));
        assertThat(representation.getNumber(), is(equalTo(bean.getNumber())));
        assertThat(representation.getNumberList(), is(equalTo(bean.getNumberList())));
        assertThat(representation.getString(), is(equalTo(bean.getString())));
        assertThat(representation.getStringList(), is(equalTo(bean.getStringList())));
        assertThat(representation.getObjectList().size(), is(equalTo(bean.getObjectList().size())));
        for (int i = 0; i < representation.getObjectList().size(); i++) {
            this.assertMockBeanRepresentationEquality(bean.getObjectList().get(i), representation.getObjectList().get(i));
        }
        assertThat(representation.getSet().size(), is(equalTo(bean.getSet().size())));
        if (!bean.getSet().isEmpty()) {
            this.assertMockBeanRepresentationEquality(bean.getSet().iterator().next(), representation.getSet().iterator().next());
        }
        assertThat(representation.getMap().size(), is(equalTo(bean.getMap().size())));
        if (!bean.getMap().isEmpty()) {
            this.assertMockBeanRepresentationEquality(bean.getMap().get("key"), representation.getMap().get("key"));
        }
    }

    /**
     * @return map
     */
    private Map<String, Integer> aMap() {
        Map<String, Integer> map = new HashMap<String, Integer>();
        map.put("1", 1);
        map.put("2", 2);
        map.put("a", 0);
        return map;
    }

}
